/*  extMDB 1.1 - an extension to gvSIG Desktop GIS to read GeoMedia MDB warehouses
 *  Copyright (C) 2008-2009  Stefano Orlando
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * This class uses code written by Julian J. Ray retrieved from:
 * http://svn.geotools.org/branches/2.5.x/modules/unsupported/geomedia/src/main/java/org/geotools/data/geomedia/attributeio/GeoMediaGeometryAdapter.java
 * with the following license:
 */
/*
 *    GeoLBS - OpenSource LocationReference Based Servces toolkit
 *    Copyright (C) 2003-2004, Julian J. Ray, All Rights Reserved
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 */

/*
 * Revision 1.1  02/05/09 Stefano Orlando
 * Revision 1.0  06/02/09 Stefano Orlando
 */
package com.iver.cit.gvsig.mdb.core;

import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Hashtable;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateArrays;
import com.vividsolutions.jts.geom.CoordinateList;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
 * Converts geometry objects between JTS geometry types and GeoMedia blobs.
 * @author Stefano Orlando
 */
public class MDBGeometryAdapter {
    // these are the Windows-based GUIDs used to identify each GeoMedia geometry type
	// not used but here for reference!
    private static String[] mGdoGuidStrings = {
        "0FD2FFC0-8CBC-11CF-ABDE-08003601B769", // Point Geometry
        "0FD2FFC8-8CBC-11CF-ABDE-08003601B769", // Oriented Point Geometry
        "0FD2FFC9-8CBC-11CF-ABDE-08003601B769", // Text Point Geometry
        "0FD2FFC1-8CBC-11CF-ABDE-08003601B769", // Line Geometry
        "0FD2FFC2-8CBC-11CF-ABDE-08003601B769", // Polyline Geometry
        "0FD2FFC3-8CBC-11CF-ABDE-08003601B769", // Polygon Geometry
        "0FD2FFC7-8CBC-11CF-ABDE-08003601B769", // Rectangle Geometry
        "0FD2FFC5-8CBC-11CF-ABDE-08003601B769", // Boundary Geometry
        "0FD2FFC6-8CBC-11CF-ABDE-08003601B769", // Geometry Collection (Hetereogeneous)
        "0FD2FFCB-8CBC-11CF-ABDE-08003601B769", // Composite Polyline Geometry
        "0FD2FFCC-8CBC-11CF-ABDE-08003601B769", // Composite Polygon Geometry
        "0FD2FFCA-8CBC-11CF-ABDE-08003601B769"  // Arc Geometry
    };
    // this is the format of non-significant component of the GDO GUID headers represented
    // as signed ints. These are written as unsigned bytes to the output stream
    private static int[] mGdoGuidByteArray = { 210, 15, 188, 140, 207, 17, 171, 222, 8, 0, 54, 1, 183, 105 };
    // used to construct JTS geometry objects
    GeometryFactory mGeometryFactory;
    // holds the mappings between GeoMedia and JTS geometries
    private Hashtable<Integer, Class<?>> mTypeMapping;
    // data related to the coordinate system type (geographic or projected)
    // and the storage space
    byte coordSystemType; // 0 --> projection; 1 --> geographic; 2 --> geocentric
    double horStorageUnitX;
    double horStorageUnitY;
    double verStorageUnit;
    double storageCenterX;
    double storageCenterY;
    double storageCenterZ;
    private class Unsupported {}
    /**
     * Creates a new MDBGeometryAdapter object.
     * @author Stefano Orlando
     */
    public MDBGeometryAdapter() {
        // we cache a JTS geometry factory for expediency
        mGeometryFactory = new GeometryFactory();
        // set up the geometry type mappings; Text, Rectangle and Arc are not supported
        mTypeMapping = new Hashtable<Integer, Class<?>>();
        // FFC0 - Point Geometry
        mTypeMapping.put(new Integer(65472), Point.class); // One-to-One correspondence
        // FFC8 - Oriented Point Geometry
        mTypeMapping.put(new Integer(65480), Point.class); // removes orientation component
        // FFC1 - Line Geometry
        mTypeMapping.put(new Integer(65473), LineString.class); // Converts simple lines to line strings
        // FFC2 - Polyline Geometry
        mTypeMapping.put(new Integer(65474), LineString.class); // One-to-One correspondence
        // FFC3 - Polygon Geometry
        mTypeMapping.put(new Integer(65475), Polygon.class); // One-to-One correspondence
        // FFC5 - Boundary Geometry
        mTypeMapping.put(new Integer(65477), Polygon.class); // One-to-One correspondence
        // FFC6 - Geometry Collection (Hetereogeneous)
        mTypeMapping.put(new Integer(65478), GeometryCollection.class); // Heterogeneous collection
        // FFCB - Composite Line Geometry
        mTypeMapping.put(new Integer(65483), MultiLineString.class); // One-to-One correspondence
        // FFCC - Composite Polygon Geometry
        mTypeMapping.put(new Integer(65484), MultiPolygon.class); // One-to-One correspondence
        // FFCA - Arc Geometry
        mTypeMapping.put(new Integer(65482), Unsupported.class); // NOT SUPPORTED
        // FFC7 - Rectangle Geometry
        mTypeMapping.put(new Integer(65479), Unsupported.class); // NOT SUPPORTED
        // FFC9 - Text Point Geometry
        mTypeMapping.put(new Integer(65481), Unsupported.class); // NOT SUPPORTED
    }
    /**
     * Converts GeoMedia blobs to JTS geometry types. Performs endian-conversion on data contained
     * in the blob.
     * @param input - GeoMedia geometry blob read from GeoMedia spatial database
     * @return JTS Geometry (3D) or <code>null</code> if the input is null
     * @throws MDBAdapterException if a geometry is not recognized or malformed
     * @author Stefano Orlando
     */
    public Geometry deSerialize(byte[] input) throws MDBAdapterException {
        Geometry geom = null;
        if (input == null) return geom;
        // 40 bytes is the minimum size for a point geometry
        if (input.length < 40) throw new MDBAdapterException("malformed_geom_error");
        ByteBuffer byteBuffer = ByteBuffer.allocate(input.length);
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        byteBuffer.put(input);
        byteBuffer.position(0);
        int uidNum = 0;
	    try {    
	        // extract the 16 byte GUID on the front end
	        // first 2 bytes (short) is the index into the type map as an unsigned short
	        uidNum = (int) (byteBuffer.getShort() & 0xffff);
	        // skip the next 14 bytes
	        int[] vals = new int[14];
	        for (int i = 0; i < 14; i++) {
	            vals[i] = byteBuffer.get();
	        }
	    }
        catch (BufferUnderflowException ex) {
        	throw new MDBAdapterException("malformed_geom_error");
        }
        Class<?> geomType = (Class<?>) mTypeMapping.get(new Integer(uidNum));
        // throws an exception if we come across an unknown geometry
        if (geomType == null) throw new MDBAdapterException("unknown_geom_error");
        // throws an exception if we come across a geometry we have not yet supported
        if (geomType == Unsupported.class) throw new MDBAdapterException("unsupported_geom_error");
        // delegates to the appropriate de-serializer
        if (geomType == Point.class) {
            geom = createPointGeometry(byteBuffer);
        } else if (geomType == LineString.class) {
            geom = createLineStringGeometry(uidNum, byteBuffer);
        } else if (geomType == MultiLineString.class) {
            geom = createMultiLineStringGeometry(byteBuffer);
        } else if (geomType == Polygon.class) {
            geom = createPolygonGeometry(uidNum, byteBuffer);
        } else if (geomType == GeometryCollection.class) {
            geom = createGeometryCollectionGeometry(byteBuffer);
        } else if (geomType == MultiPolygon.class) {
        	geom = createMultiPolygonGeometry(byteBuffer);
        }
        return geom;
    }
    /**
     * Constructs and returns a JTS Point geometry from x, y, x coordinates.
     * @param x of the point
     * @param y of the point
     * @param z of the point
     * @return JTS Point
     * @author Stefano Orlando
     */
    private Point createPointGeometry(double x, double y, double z) {
    	double[] coords = adjustCoords(x, y, z);
        return mGeometryFactory.createPoint(new Coordinate(coords[0], coords[1], coords[2]));
    }
    /**
     * Constructs and returns a JTS Point geometry by reading from the byte stream.
     * @param reader - LEDataInputStream reading from byte array containing a GeoMedia blob
     * @return JTS Point
     * @author Stefano Orlando
     * @throws MDBAdapterException if the geometry is malformed
     */
    private Point createPointGeometry(ByteBuffer reader) throws MDBAdapterException {
        try {
        	return createPointGeometry(reader.getDouble(), reader.getDouble(), reader.getDouble());
        }
        catch (BufferUnderflowException ex) {
        	throw new MDBAdapterException("malformed_geom_error");
        }
    }
    /**
     * Constructs and returns a JTS LineString geometry from an array of coordinates.
     * @param elems - array of coordinates encoded with the following schema
     * <code>[x0,y0,z0,x1,y1,z1,...]</code>
     * @return JTS LineString or <code>null</code> if the number of coordinates in the
     * input array is incorrect or the LineString is malforme (e.g., two consecutive points
     * are equal)
     * @author Stefano Orlando
     */
    private LineString createLineStringGeometry(double[] elems) {
        if (elems == null) return null;
    	CoordinateList list = new CoordinateList();
        // checks to see if the elems array is long enough
        if ((elems.length != 0) && (elems.length < 6)) {
            return null;
        }
        // watches for an incorrect number of coordinates
        if ((elems.length % 3) != 0) {
            return null;
        }
        for (int i = 0; i < elems.length;) {
        	double[] coords = adjustCoords(elems[i], elems[i + 1], elems[i + 2]);
            list.add(new Coordinate(coords[0], coords[1], coords[2]));
            i += 3;
        }
        try {
        	return mGeometryFactory.createLineString(CoordinateArrays.toCoordinateArray(list));
        }
        catch (IllegalArgumentException e) {
        	return null;
        }
    }
    /**
     * Constructs and returns a JTS LineString geometry from the byte stream.
     * @param guid - GeoMedia GUID for a line (65473) or a polyline (65474)
     * @param reader - LEDataInputStream reading from byte array containing a GeoMedia blob
     * @return JTS LineString
     * @throws MDBAdapterException if the geometry is malformed or the GUID is not recognized
     * @author Stefano Orlando
     */
    private LineString createLineStringGeometry(int guid, ByteBuffer reader) throws MDBAdapterException {
        double[] a = null;
        if (guid == 65473) { // line geometry
            a = new double[6];
            try {
	            a[0] = reader.getDouble(); // x1
	            a[1] = reader.getDouble(); // y1
	            a[2] = reader.getDouble(); // z1;
	            a[3] = reader.getDouble(); // x2
	            a[4] = reader.getDouble(); // y2
	            a[5] = reader.getDouble(); // z2;
            }
            catch (BufferUnderflowException ex) {
            	throw new MDBAdapterException("malformed_geom_error");
            }
        } else if (guid == 65474) { // polyline geometry
        	try {
	            int numOrdinates = reader.getInt();
	            a = new double[numOrdinates * 3];
	            for (int i = 0; i < numOrdinates; i++) {
	                a[3 * i] = reader.getDouble(); // xn
	                a[(3 * i) + 1] = reader.getDouble(); // yn
	                a[(3 * i) + 2] = reader.getDouble(); // zn
	            }
        	}
	        catch (BufferUnderflowException ex) {
	        	throw new MDBAdapterException("malformed_geom_error");
	        }	            
        }
        LineString lineString = createLineStringGeometry(a);
        if (lineString == null) throw new MDBAdapterException("malformed_geom_error");
        else return lineString;
    }
    /**
     * Constructs and returns a JTS MultiLineString geometry from a list of LineStrings.
     * @param lineStrings - list of LineStrings
     * @return JTS MultiLineString or <code>null</code> if the input list is null or empty
     * @author Stefano Orlando
     */
    private MultiLineString createMultiLineStringGeometry(ArrayList<Geometry> lineStrings) {
    	if ((lineStrings == null)||(lineStrings.size() == 0)) return null;
        LineString[] array = new LineString[lineStrings.size()];
        for (int i = 0; i < lineStrings.size(); i++) {
            array[i] = (LineString) lineStrings.get(i);
        }
        try {
        	return mGeometryFactory.createMultiLineString(array);
        }
        catch (IllegalArgumentException e) {
        	return null;
        }
    }
    /**
     * Constructs and returns a JTS MultiLineString geometry from the byte stream.
     * @param reader - LEDataInputStream reading from byte array containing a GeoMedia blob
     * @return JTS MultiLineString
     * @throws MDBAdapterException if the geometry is malformed
     * @author Stefano Orlando
     */
    private MultiLineString createMultiLineStringGeometry(ByteBuffer reader) throws MDBAdapterException {
        // this is to hold the geometries from the collection
    	ArrayList<Geometry> array = null;
    	try {
	    	// gets the number of items in the collection
	        int numItems = reader.getInt();
	        array = new ArrayList<Geometry>();
	        for (int i = 0; i < numItems; i++) {
	            // reads the size of the next blob
	            int elemSize = reader.getInt();
	            // recursively creates a geometry from this blob
	            byte[] elem = new byte[elemSize];
	            for (int j = 0; j < elemSize; j++) {
	                elem[j] = reader.get();
	            }
	            Geometry g = deSerialize(elem);
	            if (!(g instanceof LineString)) throw new MDBAdapterException("malformed_geom_error");
	            array.add(g);
	        }
        }
        catch (BufferUnderflowException ex) {
        	throw new MDBAdapterException("malformed_geom_error");
        }
        // now we need to append these items together
        MultiLineString multiLineString = createMultiLineStringGeometry(array);
        if (multiLineString == null) throw new MDBAdapterException("malformed_geom_error");
        else return multiLineString;
    }
    /**
     * Constructs and returns a JTS Polygon geometry from an exterior ring (Polygon)
     * and zero, one or more holes (GeometryCollection of Polygons).
     * @param shell - a JTS Polygon that constitutes the exterior ring
     * @param holes - a JTS GeometryCollection of Polygons that constitutes the interior holes
     * or <code>null</code> in case of absence of holes
     * @return JTS Polygon or <code>null</code> if the shell is <code>null</code>
     * @throws MDBAdapterException if the holes are not Polygons
     * @author Stefano Orlando
     */
    private Polygon createPolygonGeometry(Polygon shell, GeometryCollection holes)
    	throws MDBAdapterException {
    	if (shell == null) return null;
    	LinearRing extLR = (LinearRing)(shell.getExteriorRing());
    	ArrayList<LinearRing> intLR = new ArrayList<LinearRing>();
    	if (holes != null) {
    		int numHoles = holes.getNumGeometries();
    		if (numHoles == 0) intLR = null;
    		for (int i = 0; i < numHoles; i++) {
    			Geometry geom = holes.getGeometryN(i);
    			if (geom instanceof Polygon)
    				intLR.add((LinearRing)((Polygon)(holes.getGeometryN(i))).getExteriorRing());
    			else if (geom instanceof GeometryCollection) {
    				int numGeom = geom.getNumGeometries();
    				for (int j = 0; j < numGeom; j++) {
    					Geometry innerGeom = ((GeometryCollection)geom).getGeometryN(j);
    	    			if (innerGeom instanceof Polygon)
    	    				intLR.add((LinearRing)((Polygon)innerGeom).getExteriorRing());
    	    			else throw new MDBAdapterException("unsupported_geom_error");
    				}
    			}
    			else throw new MDBAdapterException("unsupported_geom_error");
    		}
    	}
    	LinearRing[] arrayIntLR = new LinearRing[intLR.size()];
    	try {
    		return mGeometryFactory.createPolygon(extLR, intLR.toArray(arrayIntLR));
        }
        catch (IllegalArgumentException e) {
        	throw new MDBAdapterException("malformed_geom_error");
        }
    }
    /**
     * Constructs and returns a JTS Polygon geometry from an array of coordinates.
     * @param elems - array of coordinates encoded with the following schema
     * <code>[x0,y0,z0,x1,y1,z1,...]</code>
     * @return JTS Polygon or <code>null</code> if the number of coordinates in the
     * input array is incorrect or the Polygon is malformed (e.g., the boundary shell
     * isn't closed)
     * @author Stefano Orlando
     */
    private Polygon createPolygonGeometry(double[] elems) {
    	if (elems == null) return null;
        CoordinateList list = new CoordinateList();
        // checks to see if the elems array is long enough
        if ((elems.length != 0) && (elems.length < 9)) {
            return null;
        }
        // watches for an incorrect number of coordinates
        if ((elems.length % 3) != 0) {
            return null;
        }
        for (int i = 0; i < elems.length;) {
        	double[] coords = adjustCoords(elems[i], elems[i + 1], elems[i + 2]);
            list.add(new Coordinate(coords[0], coords[1], coords[2]));
            i += 3;
        }
        try {
        	LinearRing ring = mGeometryFactory.createLinearRing(CoordinateArrays.toCoordinateArray(list));
        	return mGeometryFactory.createPolygon(ring, null);
        }
        catch (IllegalArgumentException e) {
        	return null;
        }        
    }
    /**
     * Constructs and returns a JTS Polygon geometry from the byte stream.
     * @param guid - GeoMedia GUID for a polygon (65475) or a boundary (65477)
     * @param reader - LEDataInputStream reading from byte array containing a GeoMedia blob
     * @return JTS Polygon
     * @throws MDBAdapterException if the geometry is malformed or the GUID is not recognized 
     * @author Stefano Orlando
     */
    private Polygon createPolygonGeometry(int guid, ByteBuffer reader) throws MDBAdapterException {
        Polygon polygon = null;
    	double[] a = null;
        if (guid == 65475) { // polygon geometry
            try {
        	int numOrdinates = reader.getInt();
	            a = new double[numOrdinates * 3];
	            for (int i = 0; i < numOrdinates; i++) {
	                a[3 * i] = reader.getDouble();
	                a[(3 * i) + 1] = reader.getDouble();
	                a[(3 * i) + 2] = reader.getDouble();
	            }
            }
            catch (BufferUnderflowException ex) {
            	throw new MDBAdapterException("malformed_geom_error");
            }
            polygon = createPolygonGeometry(a);
        } else if (guid == 0xFFC5) { // boundary geometry
        	byte[] elem = null;
        	Geometry shell = null;
        	try {
	        	int shellSize = reader.getInt();
	            elem = new byte[shellSize];
	            for (int j = 0; j < (int)shellSize; j++) {
	                elem[j] = reader.get();
	            }
	            shell = deSerialize(elem);
	            if (!(shell instanceof Polygon)) throw new MDBAdapterException("malformed_geom_error");
	            int holesSize = reader.getInt();
	            elem = null;
	            elem = new byte[holesSize];
	            for (int j = 0; j < (int)holesSize; j++) {
	                elem[j] = reader.get();
	            }
        	}
            catch (BufferUnderflowException ex) {
            	throw new MDBAdapterException("malformed_geom_error");
            }
            Geometry holes = deSerialize(elem);
            if (!(holes instanceof GeometryCollection)) throw new MDBAdapterException("malformed_geom_error");
            polygon = createPolygonGeometry((Polygon)shell, (GeometryCollection)holes);
        }
        if (polygon == null) throw new MDBAdapterException("malformed_geom_error");
        else return polygon;
    }
    /**
     * Constructs and returns a JTS MultiPolygon geometry from a list of Polygons.
     * @param polygon - list of Polygons
     * @return JTS MultiPolygon or <code>null</code> if the input list is null or empty
     * @author Stefano Orlando
     */
    private MultiPolygon createMultiPolygonGeometry(ArrayList<Geometry> polygons) {
    	if ((polygons == null)||(polygons.size() == 0)) return null;
        Polygon[] array = new Polygon[polygons.size()];
        for (int i = 0; i < polygons.size(); i++) {
            array[i] = (Polygon) polygons.get(i);
        }
        try {
        	return mGeometryFactory.createMultiPolygon(array);
    	}
    	catch (IllegalArgumentException e) {
    		return null;
    	}        
    }    
    /**
     * Constructs and returns a JTS MultiPolygon geometry from the byte stream.
     * @param reader - LEDataInputStream reading from byte array containing a GeoMedia blob
     * @return JTS MultiPolygon
     * @throws MDBAdapterException if the geometry is malformed
     * @author Stefano Orlando
     */
    private MultiPolygon createMultiPolygonGeometry(ByteBuffer reader) throws MDBAdapterException {
    	ArrayList<Geometry> array = null;
    	ArrayList<Coordinate> arrayPoints = null;
    	try {
	    	// gets the number of items in the collection
	        int numItems = reader.getInt();
	        // these are to hold the geometries from the collection
	        array = new ArrayList<Geometry>();
	        arrayPoints = new ArrayList<Coordinate>();
	        for (int i = 0; i < numItems; i++) {
	            // reads the size of the next blob
	            int elemSize = reader.getInt();
	            // recursively create a geometry from this blob
	            byte[] elem = new byte[elemSize];
	            for (int j = 0; j < elemSize; j++) {
	                elem[j] = reader.get();
	            }
	            Geometry g = deSerialize(elem);
				// case 1 --> normal
	            if (g instanceof Polygon) array.add(g);
	            // case 2 --> that strange!
	            // a simple polygon could be represented by a Composite Polygon Geometry
	            // made by a set of Polylines forming a ring
	            else if (g instanceof LineString) {
	            	// glues all the coordinates of the set of polylines in one ArrayList removing
	            	// duplicated points
	            	int start;
	            	if (arrayPoints.isEmpty() == true) start = 0;
	            	else start = 1;
	            	for (int j = start; j < ((LineString)g).getNumPoints(); j++) {
	            		arrayPoints.add(((LineString)g).getCoordinateN(j));
	            	}
	            }
	            else throw new MDBAdapterException("malformed_geom_error");
	        }
        }
        catch (BufferUnderflowException ex) {
        	throw new MDBAdapterException("malformed_geom_error");
        }        
        // case 2 above, else case 1
        if (array.isEmpty() == true) {
        	Coordinate[] coords = new Coordinate[arrayPoints.size()];
        	try {
        		array.add(mGeometryFactory.createPolygon(
        				mGeometryFactory.createLinearRing(arrayPoints.toArray(coords)),
        				null)
        			 	);
        	}
        	catch (IllegalArgumentException e) {
        		throw new MDBAdapterException("malformed_geom_error");
        	}
        }
        return createMultiPolygonGeometry(array);
    }  
    /**
     * Constructs and returns a JTS GeometryCollection geometry from the byte stream.
     * @param reader - LEDataInputStream reading from byte array containing a GeoMedia blob
     * @return JTS GeometryCollection
     * @throws MDBAdapterException if the geometry is malformed
     * @author Stefano Orlando
     */
    private GeometryCollection createGeometryCollectionGeometry(ByteBuffer reader)
    	throws MDBAdapterException {
    	ArrayList<Geometry> array = null;
    	try {
	    	// gets the number of items in the collection
	        int numItems = reader.getInt();
	        // this is to hold the geometries from the collection
	        array = new ArrayList<Geometry>();
	        for (int i = 0; i < numItems; i++) {
	            // reads the size of the next blob
	            int elemSize = reader.getInt();
	            // recursively create a geometry from this blob
	            byte[] elem = new byte[elemSize];
	            for (int j = 0; j < elemSize; j++) {
	                elem[j] = reader.get();
	            }
	            Geometry g = deSerialize(elem);
	            if (g == null) throw new MDBAdapterException("malformed_geom_error");
	            array.add(g);
	        }
        }
        catch (BufferUnderflowException ex) {
        	throw new MDBAdapterException("malformed_geom_error");
        }           
        // now we need to append these items together
        return createGeometryCollectionGeometry(array);
    }
    /**
     * Constructs and returns a JTS GeometryCollection geometry from a list of Geometries.
     * @param polygon - list of Geometries
     * @return JTS GeometryCollection or <code>null</code> if the input list is null or empty
     * or some geometry is malformed
     * @author Stefano Orlando
     */
    private GeometryCollection createGeometryCollectionGeometry(ArrayList<Geometry> geoms) {
    	if ((geoms == null)||(geoms.size() == 0)) return null;
        Geometry[] array = new Geometry[geoms.size()];
        for (int i = 0; i < geoms.size(); i++) {
            array[i] = (Geometry) geoms.get(i);
        }
        try {
        	return mGeometryFactory.createGeometryCollection(array);
        }
    	catch (IllegalArgumentException e) {
    		return null;
    	} 
    }
    /**
     * Transforms a set of three coordinates (x,y,z) considering the horizontal and vertical
     * storage units and the position of the storage center.
     * @param x - x coordinate
     * @param y - y coordinate
     * @param z - z coordinate
     * @return array of transformed coordinates (index 0 --> x; index 1 --> y; index 2 --> z)
     * @author Stefano Orlando
     */
    private double[] adjustCoords(double x, double y, double z) {
    	double[] adjustedCoords = new double[3];
    	if (coordSystemType == 0) { // projection
    		adjustedCoords[0] = x * horStorageUnitX + storageCenterX;
    		adjustedCoords[1] = y * horStorageUnitY + storageCenterY;
    		adjustedCoords[2] = z * verStorageUnit + storageCenterZ;
    	}
    	else if (coordSystemType == 1) { // geographic
    		// angles are stored in rad units
    		adjustedCoords[0] = x * (horStorageUnitX*180/Math.PI) + (storageCenterX*180/Math.PI);
    		adjustedCoords[1] = y * (horStorageUnitY*180/Math.PI) + (storageCenterY*180/Math.PI);
    		adjustedCoords[2] = z * verStorageUnit + storageCenterZ;
    	}
    	else if (coordSystemType == 2) { // geocentric
    		adjustedCoords[0] = x * horStorageUnitX + storageCenterX;
    		adjustedCoords[1] = y * horStorageUnitX + storageCenterY;
    		adjustedCoords[2] = z * horStorageUnitX + storageCenterZ;
    	}
    	return adjustedCoords;
    }
    /**
     * Stores informations related to the coordinate system type (geographic, projected or geocentric)
	 * and the storage space; this method must be called before retrieving any geometry.
     * @param coordSystemType - coordinate system type: 0 --> projection; 1 --> geographic; 2 --> geocentric
     * @param horStorageUnit - the horizontal storage unit (x/y or lon/lat)
     * @param verStorageUnit - the vertical storage unit (height or z)
     * @param storageCenterX - the x/lon position of the storage center
     * @param storageCenterY - the y/lat position of the storage center
     * @param storageCenterZ - the height/z position of the storage center
     * @author Stefano Orlando
     */
	public void setCoordTransData(byte coordSystemType,
			double horStorageUnitX,
			double horStorageUnitY,
			double verStorageUnit,
			double storageCenterX,
			double storageCenterY,
			double storageCenterZ) {
		this.coordSystemType = coordSystemType;
		this.horStorageUnitX = horStorageUnitX;
		this.horStorageUnitY = horStorageUnitY;
		this.verStorageUnit = verStorageUnit;
		this.storageCenterX = storageCenterX;
		this.storageCenterY = storageCenterY;
		this.storageCenterZ = storageCenterZ;		
	}
}
